package scala.examples.fs;

import scala.util.parsing._;
import Syntax._;
import java.io.FileInputStream;

trait Evaluator {

  type Store = LocMap[StoreItem];

  val Omega0 = new Store;
  
  // Big-bang: add the built-in $fs$ object to the store
  val fsRef = Ref(Omega0.nextLoc,"$fs")
  val Omega = Omega0.extend(Obj(null, List(
       DefDef("println", List(Arg("s", Inst(Select(Name("$fs$"), "String")))), Inst(Select(Name("$fs$"), "Unit")), BuiltinPrintln),
       DefDef("print", List(Arg("s", Inst(Select(Name("$fs$"), "String")))), Inst(Select(Name("$fs$"), "Unit")), BuiltinPrint),
       DefDef("error", List(Arg("s", Inst(Select(Name("$fs$"), "String")))), Inst(Select(Name("$fs$"), "Unit")), BuiltinError)
       )))
  
  def abort(msg: String): Pair[Store, Value] = {
    error("error: " + msg); Pair(Omega, Null)
  }

  // evaluation context E = [] | E.l | new n [] t
  // can this be factored out?
 
  def debug[t](x: t): unit = Debug.eval.log(x)
  def error[t](x: t): unit = Debug.eval.error(x)  
  
  // TODO re-typecheck every step, empirical soundness check
  def eval(s: Store, e: Term): Pair[Store, Value] = {
//    debug((show term e))
    e match {
    case Name("$fs$") => Pair(s, fsRef) // built-in stuff
    case Name("unit") => Pair(s, null) // built-in stuff
    
 //  S, v -> S, v
    case v: Value =>  
      Pair(s, v) 

 //  S, if(True, e, _) -> S, e
 //  S, if(False, _, e) -> S, e
    case If(c, then, els) =>  
       eval(s, c) match {
         case Pair(s1, True) => eval(s1, then)
         case Pair(s1, False) => els match { case Some(e) => eval(s1, e) }
         case Pair(s1, Null) => abort("if on null")
       }

 // 
    case Apply(e, args) =>     
       eval(s, e) match { 
         case Pair(s1, Closure(_, _,  bi: BuiltinTerm)) =>
           bi match {
             case BuiltinPrintln => val Pair(s2, StringLit(arg)) = eval(s1, args(0)) 
               Console.println(arg)
               Pair(s2, null)
             case BuiltinPrint => val Pair(s2, StringLit(arg)) = eval(s1, args(0)) 
               Console.print(arg)
               Pair(s2, null)
             case BuiltinError => val Pair(s2, StringLit(arg)) = eval(s1, args(0)) 
               throw new Error(arg)
           }
         case Pair(s1, Closure(formals, _,  body)) => // RED-METHOD-2
                // call-by-value:
                /*val Pair(sAfterArgs, argValsRev) = 
                    args.foldLeft(Pair(s1, List[Value]()))  ((se, argExpr) =>  {
                       val Pair(sOld, accum)  = se 
                       val Pair(sNew, argVal) = eval(sOld, argExpr)
                       Pair(sNew, argVal :: accum)});*/

               // call-by-name:
	       val sAfterArgs = s1
               val actuals = args  // CBV: argValsRev.reverse
               var bodySubst = body 
               for( val Pair(Arg(argName, _), argVal) <- formals.zip(actuals))
                 bodySubst = subst(argName, argVal) term bodySubst
                 
               eval(sAfterArgs, bodySubst)
         case Pair(s, x)  =>  abort("applying non-method"+x)
       }
       
// r meta variable for reference
 //  S, r.l ->  select( deref(S,r), l)
    case Select(e1, l) => 
      eval(s, e1) match {
	case Pair(s1, Ref(x, _)) =>
	  s1(x) match {
	    case Some(Obj(_, ms)) => // put in the store by evalStore
	      (ms filter (m => m.label == l)).last match {
		case ValSig(l, t, Some(v :Value)) if v.isInstanceOf[Value] => Pair(s1, v) // RED-VALUE
		case ValSig(l, t, _) => Pair(s1, Null) // should have been evaluated when class was eval'd
                case DefSig(l, args, t, Some(body)) => Pair(s1, Closure(args, t, body)) // RED-METHOD-1 -- TODO: perform actual closure of escaping variables
                // x.a(y*) is parsed as Apply(Select(x,a), y*)
	      }
	  }
	case Pair(s1, Null) =>
	  abort("selecting from null")
      }

      /* case block : Block =>  var scur=s; (for(val t <- block) {val Pair(snext, v) = eval(scur, t); scur=snext; yield v}).last
       */
         
    case lv @ LocalVal(xname, _, e, scope) => 
      eval(s, e) match {
        case Pair(s1, v :Value) =>       //for(val t <- scope) subst(xname, v) term t ; eval(s1, scope)
                                   eval(s1, subst(xname, v) term scope )
        case _ => abort("could not eval value for binding "+lv)
      }

    case New(p) => 
      eval(s, p)._2 match {
	case Null =>
	  abort("new null");
	case c @ Ref(z, _) =>
	  val refO = s.nextLoc; 
	  val ds = unfold(refO, s, z); // flatten members of class s(z)
          val o = Obj(c, ds) // new object of class c, with members ds
          val newS = s.extend(o) // newS(x)==o
 
          // 
	  evalStore(refO, newS) match { 
	    case Omega => Pair(Omega, Null)
	    case s1 => Pair(s1, Ref(refO, "" )) // TODO
          }
      }
      
    case ClassDef(xname, ClassSig(ps, self, st, ms), t) =>
      val vs = ps map (p => eval(s, p)._2);
      //debug(xname+": "+ vs)
      if (vs exists (Null ==)) Pair(Omega, Null)
      else {
	val newClassLoc = s.nextLoc;
        val newClass = ClassSig(vs, self,st, ms)
        val newStore = s.extend(newClass)
	eval(newStore, subst(xname, Ref(newClassLoc, xname)) term t)
      }
  }
  }

  // evaluate all ValDef's in the object referenced by x
  def evalStore(refO: Loc, s: Store): Store = s(refO) match {
    case Some(Obj(c, ms)) => 
      def isEvaluated(m: Member) = m match {
        case ValSig(_, _, Some(e)) if (!e.isInstanceOf[Value]) => false // a nested class is rewritten to a valdef
        case _ => true
      }

      (ms span isEvaluated) match { // handle all cases where isEvaluated returns false (nice application for dependent types)
        case Pair(evalMs, Nil) => s // all members have been evaluated
        case Pair(evalMs, ValSig(l, t, Some(e)) :: ums) =>
	  eval(s, e) match {
            case Pair(Omega, _) => Omega
            case Pair(s1, v) => 
                evalStore(refO, s1.update(refO, Obj(c, evalMs ::: ValDef(l, t, v) :: ums)))
          }
      }
  }

  // x will become a reference to an instance of the class referred to by y in the store s
  // unfold all the mixed in members of y and replace the self variable by x
  def unfold(x: Loc, s: Store, y: Loc): List[Member] = {
    s(y) match {
      case Some(ClassSig(parents, selfName, _, ms)) =>
        List.flatten(parents map { 
                case Ref(z, _) => unfold(x, s, z) 
                case Name(n) => null // TODO class hasn't been eval'd yet (e.g., nested class or defined below)
                }) ::: 
          (ms map (m => subst(selfName, Ref(x, selfName)).member(m)))
    }
  }
}

object EvalTest extends FSParser with Evaluator{ 

  def main(args: Array[String]): unit = {
    //debug("<fs>\n"); 
    try {
    val t = parse(new CharInputStreamIterator(new FileInputStream(args(0))));
    if (t != null) {
      /*debug("<generatedTree>\n"); 
      debug(show.term(t));
      debug("\n</generatedTree>"); */
      //debug("type: "+(show typ (new Typer).typeOf(t)))
      eval(Omega, t);
    }
    }finally {
    //debug("</fs>\n"); 
    }
  }
}
    